#include "Board.h"
#include <wx/dcclient.h>
#include <cassert>
#include <map>
#include <cstdlib>
#include <ctime>
#include <optional>
#include "icon/blank_tile.xpm"
#include "icon/flag_tile.xpm"
#include "icon/mine_tile.xpm"
#include "icon/bomb_tile.xpm"
#include "icon/tile0.xpm"
#include "icon/tile1.xpm"
#include "icon/tile2.xpm"
#include "icon/tile3.xpm"
#include "icon/tile4.xpm"
#include "icon/tile5.xpm"
#include "icon/tile6.xpm"
#include "icon/tile7.xpm"
#include "icon/tile8.xpm"

#define PTR this

const int Board::timerId = wxNewId();

BEGIN_EVENT_TABLE(Board,wxPanel)
    //(*EventTable(Board)
    EVT_PAINT(Board::OnPaint)
    EVT_TIMER(timerId,Board::OnTimer)
    //*)
END_EVENT_TABLE()

Board::Board(wxWindow *parent, wxWindowID winid, const wxPoint& pos
             , const wxSize& size, long style, const wxString& name)
    :wxPanel(parent,winid,pos,size,style,name) {
    ptr.reset(new wxBitmap[13] {{blank_tile},{flag_tile},{bomb_tile},{mine_tile}
        ,{tile0}, {tile1},{tile2},{tile3},{tile4},{tile5},{tile6},{tile7},{tile8}});
    tile = ptr.get()+4;
    srand(time(nullptr));
}

void Board::OnPaint(wxPaintEvent &event) {
    wxPaintDC dc(this);
    FOREACH(pt,ele) {
        dc.DrawBitmap(tile[ele.outer == OPEN ?ele.inner :ele.outer], pt.x*TILESIZE,pt.y*TILESIZE);
    }
}

void Board::OnTimer(wxTimerEvent &event) {
    static bool f = true;
    wxClientDC dc(this);
    for (auto &pt : wrong_mine) {
        auto &t = pool[toIndex(pt)];
        dc.DrawBitmap(tile[f == true ?t.inner :FLAG], pt.x*TILESIZE,pt.y*TILESIZE);
    }
    f = !f;
}

void Board::drawTile(const Point &pt, int idx) {
    assert(idx >= BLANK && idx < 9);
    wxClientDC dc(this);
    dc.DrawBitmap(tile[idx], pt.x*TILESIZE,pt.y*TILESIZE);
}

void Board::resize(size_t row, size_t col) {
    pool.resize(row*col);
    Row = row;
    Col = col;
}

size_t Board::size() const {
    return pool.size();
}

Tile& Board::at(const Point &pt) {
    assert(valid(pt));
    return pool[toIndex(pt)];
}

bool Board::valid(const Point &pt) const {
    return pt.x >= 0 && pt.x < Col && pt.y >= 0 && pt.y < Row;
}

size_t Board::toIndex(const Point &pt) const {
    return pt.x+pt.y*Col;
}

Point Board::toPoint(size_t idx) const {
    return {idx%Col, idx/Col};
}

template<class K, class T>
std::optional<T> operator>>(const std::map<K,T> &m, const K &key) {
    return m.find(key) != m.end() ?m.at(key) :std::optional<T>{};
}

void Board::initMine(int mine_num,const Point &pt) {
    std::vector<int> mines(mine_num);
    std::map<int,int> m;
    int limit = size() - 1;

    m[toIndex(pt)] = limit--;
    AROUND(pt, pt1, ele) {
        m[toIndex(pt1)] = limit--;
    }
    for (auto i = 0; i < mine_num; i++, limit--) {
        int n = rand() % limit;
        mines[i] = (m>>n).value_or(n);
        m[n] = (m>>limit).value_or(limit);
    }
    for (auto idx : mines) {
        Point pt = toPoint(idx);
        AROUND(pt, pt1, ele) {
            ele.inner++;
        }
    }
    for (auto idx : mines) {
        pool[idx].inner = MINE;
    }
}

Stats Board::Count(const Point &pt) {
    Stats res;
    AROUND(pt, pt1, ele) {
        if (ele.outer == BLANK) res.blank++;
        if (ele.outer == FLAG) res.flag++;
    }
    return res;
}
